package htmlsoup

import (
	"bytes"
	"fmt"
	"strings"

	"net/url"

	"code.google.com/awwwe-browser/data"
)

// docNode.attributes

type nodeAttribute struct {
	prefix, name, value string
}

func newNodeAttribute() *nodeAttribute {
	var v nodeAttribute
	return &v
}

type nodeAttributeArray struct {
	attributes []*nodeAttribute
}

func buildNodeAttributeArray() nodeAttributeArray {
	var thisNaa nodeAttributeArray
	thisNaa.attributes = make([]*nodeAttribute, 0, 5)
	return thisNaa
}

func (na *nodeAttribute) escapeValue() {
	if strings.Contains(na.value, data.TagStartString) {
		na.value = strings.Replace(na.value, data.TagStartString, data.GreaterThanCharRefString, -1)
	}
	if strings.Contains(na.value, data.DQuoteString) {
		na.value = strings.Replace(na.value, data.TagStartString, data.DQuoteCharRefString, -1)
	}
}

func (thisNaa *nodeAttributeArray) append(na *nodeAttribute) {
	var test string
	test = strings.Trim(na.name, data.WhiteSpaceString)
	if len(test) == 0 {
		return
	}
	// this attribute has a name
	na.escapeValue()
	length := len(thisNaa.attributes)
	if length == cap(thisNaa.attributes) {
		temp := make([]*nodeAttribute, length+1, length+5)
		for i := 0; i < length; i++ {
			temp[i] = thisNaa.attributes[i]
		}
		thisNaa.attributes = temp
	} else {
		thisNaa.attributes = thisNaa.attributes[:length+1]
	}
	thisNaa.attributes[length] = na
}

func (thisNaa *nodeAttributeArray) get(name string) *nodeAttribute {
	for _, attr := range thisNaa.attributes {
		if attr.name == name {
			return attr
		}
	}
	return nil
}

func (thisNaa *nodeAttributeArray) delete(target *nodeAttribute) {
	length := len(thisNaa.attributes)
	for i := 0; i < length; i++ {
		if thisNaa.attributes[i] == target {
			copy(thisNaa.attributes[i:], thisNaa.attributes[i+1:])
			thisNaa.attributes = thisNaa.attributes[:length-1]
			break
		}
	}
}

func (thisNaa *nodeAttributeArray) containsName(name string) bool {
	for _, attr := range thisNaa.attributes {
		if attr.name == name {
			return true
		}
	}
	return false
}

func (thisNaa *nodeAttributeArray) writeToBuffer(bfr *bytes.Buffer) {
	length := len(thisNaa.attributes)
	for i := 0; i < length; i++ {
		bfr.Write(data.SpaceBytes)
		attribute := thisNaa.attributes[i]
		prefix := attribute.prefix
		if len(prefix) > 0 {
			bfr.WriteString(prefix)
			bfr.Write(data.ColonBytes)
		}
		bfr.WriteString(attribute.name)
		bfr.Write(data.EqualBytes)
		bfr.Write(data.DQuoteBytes)
		bfr.WriteString(attribute.value)
		bfr.Write(data.DQuoteBytes)
	}
}

// tagNode.attributes methods

func (thisNode *SoupNode) parseAttributes(bb []byte) {
	var temp nodeAttributeArray
	temp = thisNode.preParseAttributes(bb)
	if thisNode.nodeType == nodeTypeTag {
		// html tag parsing
		for _, attr := range temp.attributes {
			if ((len(attr.name) > 2 && attr.name[0:2] != "on") &&
				attr.name != "xmlns" && attr.prefix != "xmlns") ||
				len(attr.name) <= 2 {
				// don't add javascript event handlers
				// don't add namespace attributes
				// ignore namespaces
				// ignore names with "-" except for the awwwe attribute id
				attr.prefix = data.EmptyString
				thisNode.attributes.append(attr)
			}
		}
	} else {
		// no more parsing of the attributes needed
		for _, v := range temp.attributes {
			thisNode.attributes.append(v)
		}
	}
}

func (thisNode *SoupNode) preParseAttributes(bb []byte) (returnTemp nodeAttributeArray) {
	var prefix, name, value string
	var rr []rune
	returnTemp = buildNodeAttributeArray()
	rr = bytes.Runes(bb)
	for {
		rr, prefix, name, value = parseAttribute(rr)
		//if len(rr) == 0 || len(name) == 0 {
		if len(name) == 0 {
			break
		}
		if name != "style" &&
			(len(value) <= 11 || (len(value) > 11 && value[:11] != "javascript:")) {
			// name is not style or javascript so add the attribute
			var na nodeAttribute
			na.prefix = prefix
			na.name = name
			na.value = value
			returnTemp.append(&na)
		}
	}
	return
}

func parseAttribute(rr []rune) (returnNextrr []rune, returnPrefix, returnName, returnValue string) {
	last := len(rr)
	var tempLen int
	var prefixrr, namerr, valuerr []rune
	var quoteRune rune
	var i, j int
	var start, end int
	returnNextrr = rr[0:0]
	returnPrefix, returnName, returnValue = data.EmptyString, data.EmptyString, data.EmptyString
	// skip white space before name
	for i = 0; i < last; i++ {
		if rr[i] != data.SpaceRune {
			break
		}
	}
	if i < last {
		// at the start of name or prefix:name
		start = i
		// find the end of the name
		for i = start + 1; i < last; i++ {
			if rr[i] == data.SpaceRune || rr[i] == data.EqualsRune {
				break
			}
		}
		end = i
		namerr = rr[start:end]
		returnName = strings.ToLower(data.RunesToUTF8String(namerr))
		// if no value ex: hidden
		// make a value ex: hidden="hidden"
		returnValue = returnName
		// skip space between name and '='
		if i < last && rr[i] == data.SpaceRune {
			for i = i + 1; i < last; i++ {
				if rr[i] == data.EqualsRune {
					break
				}
			}
		}
		if i < last && rr[i] == data.EqualsRune {
			// i is '=' or the end
			// end is the char after the name
			// split prefix name if needed
			prefixrr = rr[0:0]
			namerr = rr[start:end]
			tempLen = len(namerr)
			for j = 0; j < tempLen; j++ {
				if namerr[j] == data.ColonRune {
					// split prefix : name
					prefixrr = namerr[:j]
					namerr = namerr[j+1:]
					break
				}
			}
			// have prefix name
			// found '='
			// skip over white space between '=' and value
			for i = i + 1; i < last; i++ {
				if rr[i] != data.SpaceRune {
					break
				}
			}
			if i < last {
				// found start of quoted or unquoted value
				if rr[i] == data.DQuoteRune || rr[i] == data.SQuoteRune {
					// quoted value
					quoteRune = rr[i]
					start = i + 1
					for i = start; i < last; i++ {
						if rr[i] == quoteRune {
							break
						}
					}
				} else {
					start = i
					for i = start; i < last; i++ {
						if rr[i] == data.SpaceRune {
							break
						}
					}
				}
				// found the end of the value
				valuerr = rr[start:i]
				// set the returns
				if i < last-1 {
					returnNextrr = rr[i+1:]
				} else {
					// nothing left
					returnNextrr = rr[0:0]
				}
				returnPrefix = strings.ToLower(data.RunesToUTF8String(prefixrr))
				returnName = strings.ToLower(data.RunesToUTF8String(namerr))
				valuebb := data.RunesToUTF8Bytes(valuerr)
				valuebb = bytes.Replace(valuebb, data.AmpersandCharRefBytes, data.AmpersandBytes, -1)
				valuebb = bytes.Replace(valuebb, data.LessThanCharRefBytes, data.LessThanBytes, -1)
				valuebb = bytes.Replace(valuebb, data.GreaterThanCharRefBytes, data.GreaterThanBytes, -1)
				returnValue = data.BytesToUTF8String(valuebb)
			}
		}
	}
	return
}

func checkNoAttribute(node *SoupNode) {
}

func checkBaseTagHrefAttribute(node *SoupNode) {
	attr := node.attributes.get("href")
	if attr != nil && len(attr.value) > 0 {
		ref, err := url.Parse(attr.value)
		if err == nil {
			node.statics.baseURI = node.statics.location.ResolveReference(ref)
			attr.value = node.statics.baseURI.String()
		} else {
			attr.value = fmt.Sprint(err)
		}
	}
}

func checkLinkTagHrefAttribute(node *SoupNode) {
	// <link rel="alternate" type="application/rss+xml" href="http://www.thecommonsenseshow.com/feed/" title="Dave Hodges - The Common Sense Show latest posts">
	attr := node.attributes.get("type")
	if attr != nil && strings.Contains(attr.value, "html") {
		node.checkHTMLUrlAttribute("action")
	} else {
		node.checkUrlAttribute("href")
	}
}

func checkHrefAttribute(node *SoupNode) {
	node.checkHTMLUrlAttribute("href")
}

func checkFormActionAttribute(node *SoupNode) {
	node.checkHTMLUrlAttribute("action")
}

func checkSrcAttribute(node *SoupNode) {
	node.checkUrlAttribute("src")
}

func (thisNode *SoupNode) checkHTMLUrlAttribute(name string) {
	var value string
	attr := thisNode.attributes.get(name)
	if attr != nil {
		value = thisNode.fixUrl(attr.value)
		/*
			isHTML := mimetype.IsURLMimeTypeHTML(value)
			// convert www.stuff.com to awwwe.stuff.com
			if isHTML {
			    value = mimetype.AWWWEURL(value)

			}
		*/
		if len(value) > 0 {
			attr.value = value
		} else {
			thisNode.attributes.delete(attr)
		}
	}
}

func (thisNode *SoupNode) checkUrlAttribute(name string) {
	attr := thisNode.attributes.get(name)
	if attr != nil {
		value := thisNode.fixUrl(attr.value)
		if len(value) > 0 {
			attr.value = value
		} else {
			thisNode.attributes.delete(attr)
		}
	}
}

func (thisNode *SoupNode) fixUrl(href string) string {
	var scheme string
	var parts []string
	var i int
	var path string
	parts = strings.Split(href, "#")
	href = parts[0]
	if len(href) >= 3 && href[0:3] == "://" {
		return thisNode.statics.baseURI.Scheme + href
	} else if len(href) >= 2 && href[0:2] == "//" {
		return thisNode.statics.baseURI.Scheme + data.ColonString + href
	} else if len(href) >= 1 && href[0:1] == "/" {
		path = thisNode.statics.baseURI.Scheme + "://" + thisNode.statics.baseURI.Host + thisNode.statics.baseURI.Path
		i = len(path)
		if path[i-1:i] == "/" {
			path += href[1:]
		} else {
			path += href
		}
		return path
	} else if len(href) == 0 {
		return thisNode.statics.location.String()
	}
	// if there is no scheme then build the url
	i = strings.Index(href, data.ColonString)
	if i >= 0 {
		scheme = href[0:i]
	} else {
		scheme = data.EmptyString
	}
	if len(scheme) == 0 ||
		strings.Index(scheme, "/") >= 0 ||
		strings.Index(scheme, "?") >= 0 ||
		strings.Index(scheme, "&") >= 0 {
		// no scheme so determine how to complete the url
		if href[0:1] == "/" {
			// use the host and virtual folder
			bfr := bytes.NewBuffer(data.EmptyBytes)
			bfr.WriteString(thisNode.statics.baseURI.Scheme)
			bfr.WriteString("://")
			bfr.WriteString(thisNode.statics.baseURI.Host)
			bfr.WriteString(href)
			return bfr.String()
		} else {
			// use the host and current folder
			return thisNode.statics.baseURI.String() + href
		}
	} else {
		// there is a scheme so do nothing
		return href
	}
}
